
<!DOCTYPE HTML>
<html lang="en">
<head>
  <title>three.js - video - deformation shader - webgl</title>
  <meta charset="utf-8">
  <style type="text/css">
    body {
      color: #fff;
      font-family: Monospace;
      font-size: 13px;
      text-align: center;

      background-color: #000;
      margin: 0;
      overflow: hidden;
    }

    #info {
      color: #ffffff;
      position: absolute;
      top: 0;
      width: 100%;
      padding: 5px;
      z-index:100;
    }

    a {
      color: gold;
    }

  </style>
  <script type="text/javascript" src="../shared/gui.min.js"></script>
  <script type="text/javascript" src="../shared/RequestAnimationFrame.js"></script>
  <script type="text/javascript" src="ThreeWebGL.js"></script>
  <script type="text/javascript" src="ThreeExtras.js"></script>
</head>
<body>

<div id="container"></div>
<div id="info">
  <a href="hxxp://github.com/mrdoob/three.js" target="_blank">three.js</a> -
  three.js - video - deformation shader - webgl
</div>

<script type="text/javascript" id="main-code">

var viewWidth =  window.innerWidth;
var aspect = 2.35;
var viewHeight = viewWidth/aspect;
var shared = {};
var player;
var gridModel = "grid.js";
var params, gui;
var VIDEO_OPAQUE = 1;
var VIDEO_OPAQUE_DISTORT = 2;
var VIDEO_KEYED = 3;
var VIDEO_KEYED_DISTORT = 4;
var camera, scene;

var VideoPlane = function(layer, conf){
  var texture, duration, interval, shader, video, currentTime, material, wireMaterial;
  var config = conf;
  var hasDistortion = false;
  var hasKey = false;

  var polyTrail = new PolyTrail();

  var that = this;

  video = document.createElement('video');
  video.src = layer.path;

  texture = new THREE.Texture(video);
  texture.minFilter = THREE.LinearFilter;
  texture.magFilter = THREE.LinearFilter;

  switch (layer.shaderId) {
    case VIDEO_OPAQUE:
      shader = VideoShaderSource.opaque;
      break;
    case VIDEO_KEYED_DISTORT:
      shader = VideoShaderSource.distortSmartalpha;
      hasDistortion = true;
      hasKey = true;
      break;
  }

  var uniforms = THREE.UniformsUtils.clone(shader.uniforms);
  uniforms['map'].texture = texture;

  if (hasDistortion) {
    uniforms['mouseXY'].value = new THREE.Vector2(0, 0);
    uniforms['aspect'].value = aspect;
    uniforms['mouseSpeed'].value = 1;
    uniforms['mouseRad'].value = 1;

  }

  if (hasKey) {

    uniforms['colorScale'].value = layer.colorScale;
    uniforms['threshold'].value = layer.threshold;
    uniforms['alphaFadeout'].value = layer.alphaFadeout;

  }

  material = new THREE.MeshShaderMaterial({

        uniforms: uniforms,
        vertexShader: shader.vertexShader,
        fragmentShader: shader.fragmentShader,
        depthTest: false

      });

  //
  if(!layer.width) layer.width = (hasDistortion) ? 1.104 : 1;
  if(!layer.height) layer.height = (hasDistortion) ? 1.24 : 1;

  if(hasDistortion)
    this.mesh = new THREE.Mesh( config.grid, material );
  else
    this.mesh = new THREE.Mesh( new THREE.Plane(1,1,1,1), material );


  this.mesh.scale.x = layer.width;
  this.mesh.scale.y = layer.height;
  this.mesh.position.z = layer.z;
  this.mesh.scale.x *= Math.abs(layer.z) * config.adj * config.aspect;
  this.mesh.scale.y *= Math.abs(layer.z) * config.adj;

  this.loaded = false;

  this.init = function(t, mouseX, mouseY) {
    interval = setInterval(function(){
      if (video.readyState === video.HAVE_ENOUGH_DATA) {
        video.currentTime = video.duration * t;
        this.loaded = true;
        clearInterval( interval );
        that.start();
      }
    }, 1000 / 24);
  };

  this.start = function(t, mouseX, mouseY) {
    polyTrail.set(-mouseX * config.aspect, -mouseY);
    video.play();
    video.currentTime = video.duration * t;
    interval = setInterval(function(){
      if (video.readyState === video.HAVE_ENOUGH_DATA) {
        if (video.currentTime >= video.duration) video.currentTime = 0;
        texture.needsUpdate = true;
      }
    }, 1000 / 24);
  };

  this.stop = function() {
    video.pause();
    clearInterval( interval );
  };

  this.updateUniform = function(mouseX, mouseY, mouseSpeed, mouseRad) {
    if(!hasDistortion) return;

    polyTrail.target.x = -mouseX * config.aspect;
    polyTrail.target.y = -mouseY;
    polyTrail.update();

    for (var i = 0; i <= 4; i++) {
      material.uniforms['trail' + i].value.x = params.polyDetail * polyTrail.s[i*2*params.trail].x;
      material.uniforms['trail' + i].value.y = params.polyDetail * polyTrail.s[i*2*params.trail].y+0.0001*i;
    }
    material.uniforms['mouseXY'].value.x = params.polyDetail * -mouseX * config.aspect;
    material.uniforms['mouseXY'].value.y = params.polyDetail * -mouseY;
    material.uniforms['mouseSpeed'].value = mouseSpeed;

    material.uniforms['mouseRad'].value = params.radius;
    material.uniforms['polyRandom'].value = params.random;
    material.uniforms['polyDetail'].value = params.polyDetail;
    material.uniforms['bulge'].value = params.bulge;
    material.uniforms['softEdge'].value = params.softEdge;

  };

};

var VideoPlayer = function(shared, layers, conf){
  var that = this;
  var oldTime = new Date().getTime();

  var config = {};
  var planes = [];
  var gridLoaded = false;

  var renderer = shared.renderer;

  var mouseX = 0, mouseY = 0;
  var mouseOldX = 0, mouseOldY = 0;
  var mouseNewX = 0, mouseNewY = 0;
  var mouseU = 0, mouseV = 0;
  var mouseRad = 1;
  var mouseSpeed = 1;
  var targetPos;

  this.init = function(){

    config.prx = conf.paralaxHorizontal || 0;
    config.pry = conf.paralaxVertical || 0;
    config.tgd = 1500;

    var onGrid = function(geometry){
      config.grid = geometry;
      that.onLoad();
    };

    var gridLoader = new THREE.JSONLoader();
    gridLoader.load( { model: gridModel, callback: onGrid } );
  };

  this.onLoad = function() {
    gridLoaded = true;

    document.addEventListener('mousemove', this.mouseMove, false);
    targetPos = new THREE.Vector2(0,0);

    config.fov = 54;
    config.aspect = 2.35;
    config.adj = Math.tan( config.fov * Math.PI / 360 ) * 2;
    config.near = 1;
    config.far = 2000;

    camera = new THREE.Camera( config.fov, config.aspect, config.near, config.far );
    camera.target.position = new THREE.Vector3(0, 0, config.tgd * -1);
    camera.updateMatrix();

    scene = new THREE.Scene();
    scene.addLight( new THREE.AmbientLight( 0x000000 ) );

    for(var i = 0; i < layers.length; i++) {
      var p = new VideoPlane(layers[i], config);
      planes.push(p);
      scene.addObject(p.mesh);
    }
    player.show(0);
    animate();
  };

  this.show = function(progress) {
    for (var i = 0; i < planes.length; i++) {
      planes[i].init(progress, mouseX, mouseY);
    }
  };

  this.hide = function(){
    for (var i = 0; i < planes.length; i++) {
      planes[i].stop();
    }
  };

  this.update = function(progress, delta, time) {
    time = new Date().getTime();
    delta = time - oldTime;
    oldTime = time;

    if(!gridLoaded) return;

    mouseNewX = mouseX;
    mouseNewY = mouseY;

    var vX = Math.abs(limitSpeed(mouseNewX-mouseOldX,0.05)/delta);
    var vY = Math.abs(limitSpeed(mouseNewY-mouseOldY,0.05)/delta);

    mouseSpeed += (700*(vX+vY) - mouseSpeed)/20;

    mouseOldX = mouseX;
    mouseOldY = mouseY;

    function limitSpeed(speed, limit){
      return Math.max(Math.min(speed,limit),-limit);
    }

    targetPos.x = mouseX * config.prx;
    targetPos.y = mouseY * config.pry;

    camera.position.x += (targetPos.x - camera.position.x) / 2;
    camera.position.y += (targetPos.y - camera.position.y) / 2;
    for (var i = 0; i < planes.length; i++) {
      planes[i].updateUniform(mouseX, mouseY, mouseSpeed, mouseRad );
    }

    renderer.render( scene, camera );

  };

  var windowHalfX = window.innerWidth >> 1;
  var windowHalfY = window.innerHeight >> 1;
  this.mouseMove = function(e){
    mouseX = (event.clientX - windowHalfX) / -windowHalfX;
    mouseY = (event.clientY - windowHalfY) / windowHalfY;
    mouseU = (event.clientX) / viewWidth;
    mouseV = (event.clientY) / viewHeight / aspect;

  };
};

var VideoShaderSource = {
  opaque: {

    uniforms: {
      "map" : { type: "t", value: 0, texture: null }
    },

    vertexShader: [
      "varying vec2 vUv;",

      "void main() {",
      "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
      "vUv = uv;",
      "}"
    ].join("\n"),

    fragmentShader: [
      "uniform sampler2D map;",
      "varying vec2 vUv;",

      "void main() {",
      "gl_FragColor = texture2D( map, vUv );",
      "}"
    ].join("\n")

  },

  distortSmartalpha: {

    uniforms:{
      "aspect": { type: "f", value: 0 },
      "map": { type: "t", value: 0, texture: null },
      "colorScale": { type: "f", value: 1 },
      "threshold": { type: "f", value: 0.5 },
      "alphaFadeout": { type: "f", value: 0.5 },
      "mouseXY": { type: "v2", value: new THREE.Vector2() },
      "trail0": { type: "v2", value: new THREE.Vector2() },
      "trail1": { type: "v2", value: new THREE.Vector2() },
      "trail2": { type: "v2", value: new THREE.Vector2() },
      "trail3": { type: "v2", value: new THREE.Vector2() },
      "trail4": { type: "v2", value: new THREE.Vector2() },
      "mouseSpeed": { type: "f", value: 1. },

      "mouseRad": { type: "f", value: 1. },
      "polyRandom": { type: "f", value: 1. },
      "polyDetail": { type: "f", value: 1. },
      "bulge": { type: "f", value: 1. },
      "softEdge": { type: "f", value: 0. }
    },

    vertexShader: [
      "uniform vec2 mouseXY;",
      "uniform vec2 trail0;",
      "uniform vec2 trail1;",
      "uniform vec2 trail2;",
      "uniform vec2 trail3;",
      "uniform vec2 trail4;",
      "uniform float aspect;",
      "uniform float mouseSpeed;",
      "uniform float mouseRad;",
      "uniform float polyRandom;",
      "uniform float polyDetail;",
      "uniform float bulge;",

      "varying vec2 vUv;",
      "varying vec2 vUvPoly;",
      "varying float distance;",
      "varying float distancePoly;",

      "varying vec2 closest;",

      "vec2 getClosest(vec2 p, vec2 l1, vec2 l2){",

      "vec2 pl1 = p - l1;",
      "vec2 l2l1 = l2 - l1;",

      "float dot = pl1.x * l2l1.x + pl1.y * l2l1.y;",
      "float len_sq = pow(length(l2l1),2.);",
      "float param = dot / len_sq;",

      "if(param < 0.) return l1;",
      "else if(param > 1.) return l2;",
      "else return (l1 + l2l1 * param);",
      "}",

      "float getDistance(vec2 p, vec2 li){",
      "return 1. - sqrt( (p.x-li.x)*(p.x-li.x) + (p.y-li.y)*(p.y-li.y) );",
      "}",

      "void main() {",

      "vUvPoly = uv+vec2(normal.x,normal.y);",

      "vUv = (uv-vec2(.5))/polyDetail+vec2(.5);",

      "vUvPoly = (vUvPoly-vec2(.5))/polyDetail+vec2(.5);",

      "gl_Position = projectionMatrix * modelViewMatrix * vec4( position*(1./polyDetail), 1.0 );",

      "vec4 glPosPoly = projectionMatrix * modelViewMatrix * vec4( position-vec3(-normal.x,normal.y,0.), 1.0 );",

      "vec2 projPos = vec2(aspect,1.)*vec2(gl_Position.x/gl_Position.z,gl_Position.y/gl_Position.z);",
      "vec2 projPosPoly = vec2(aspect,1.)*vec2(glPosPoly.x/glPosPoly.z,glPosPoly.y/glPosPoly.z);",

      "float dist = (normal.z*2.)*polyRandom + 1.-polyRandom;",
      "vec2 closestTrailPoly0 = getClosest(projPosPoly,trail0,trail1);",
      "vec2 closestTrailPoly1 = getClosest(projPosPoly,trail1,trail2);",
      "vec2 closestTrailPoly2 = getClosest(projPosPoly,trail2,trail3);",
      "vec2 closestTrailPoly3 = getClosest(projPosPoly,trail3,trail4);",

      "float distanceTrailPoly0 = 1.0*dist * getDistance(projPosPoly,closestTrailPoly0);",
      "float distanceTrailPoly1 = 0.8*dist * getDistance(projPosPoly,closestTrailPoly1);",
      "float distanceTrailPoly2 = 0.6*dist * getDistance(projPosPoly,closestTrailPoly2);",
      "float distanceTrailPoly3 = 0.4*dist * getDistance(projPosPoly,closestTrailPoly3);",

      "distancePoly = mouseRad*max(0.,max(distanceTrailPoly0,max(distanceTrailPoly1,max(distanceTrailPoly2,distanceTrailPoly3))));",

      "distance = mouseRad*max(0.,1.-length(projPos-trail0  ));",
      "gl_Position.xy = gl_Position.xy + normalize(projPos.xy-trail0)*vec2(bulge*distance*mouseSpeed*50.);",

      "}"
    ].join("\n"),

    fragmentShader: [

      "uniform sampler2D map;",
      "uniform float colorScale;",
      "uniform float threshold;",
      "uniform float alphaFadeout;",
      "uniform float softEdge;",

      "varying vec2 vUv;",
      "varying vec2 vUvPoly;",
      "varying float distancePoly;",
      "varying float distance;",
      "varying vec2 closest;",

      "void main() {",
      "vec4 c = texture2D( map, vec2( vUv.x * 0.6666, vUv.y ) );",
      "vec4 a = texture2D( map, vec2( 0.6666 + vUv.x * 0.3333, vUv.y ) );",
      "c.a = a.r;",

      "vec4 cPoly = texture2D( map, vec2( vUvPoly.x * 0.6666, vUvPoly.y ) );",
      "vec4 aPoly = texture2D( map, vec2( 0.6666 + vUvPoly.x * 0.3333, vUvPoly.y ) );",
      "cPoly.a = aPoly.r;",

      "if ((distancePoly)>0.5) c = cPoly; ",
      "gl_FragColor = mix(c,cPoly,max(0.,distancePoly*softEdge));",
      "}"

    ].join("\n")

  }

};

function init(){

  shared.container = document.createElement('div');
  document.body.appendChild(shared.container);

  try {
    shared.renderer = new THREE.WebGLRenderer();
    shared.renderer.setSize(viewWidth, viewHeight);
    shared.container.appendChild(shared.renderer.domElement);
  }
  catch (e) {
    console.log(e);
  }

  var layers = [
    {
      path: "/files/videos/city/s06_layer02.webm",
      shaderId: VIDEO_OPAQUE,
      z: -1400
    },
    {
      path: "/files/videos/city/s06_layer01.webm",
      shaderId: VIDEO_KEYED_DISTORT,
      z: -900,
      colorScale: .99,
      threshold: .45,
      alphaFadeout: .35
    }
  ];

  var conf = {
    paralaxHorizontal: 40,
    paralaxVertical: 10
  };

  player = new VideoPlayer(shared, layers, conf);
  player.init();
}

function animate(){
  player.update();
  requestAnimationFrame(animate);
}

initGui();
init();

function PolyTrail(){
  this.target = new THREE.Vector2();
  this.s = [];
  for (var i = 0; i <= 32; i++) {
    this.s[i] = new THREE.Vector2();
  }
}
PolyTrail.prototype.set = function(x,y){
  for (var i = 32; i >= 0; i = i - 1) {
    this.s[i] = new THREE.Vector3(x,y,0);
  }
};
PolyTrail.prototype.update = function(){
  for (var i = 32; i > 0; i = i - 1) {
    this.s[i].x = this.s[i - 1].x;
    this.s[i].y = this.s[i - 1].y;
  }
  this.s[0].x = this.target.x;
  this.s[0].y = this.target.y;
};

function initGui(){
  params = {
    "radius": 0.95,
    "trail": 4,
    "random": 0.0,
    "bulge": 0.65,
    "polyDetail": 0.45,
    "softEdge": 0.77,
    "softTail": 0
  };
  gui = new GUI();
  gui.domElement.style.zIndex = 100;
  gui.add(params, "radius", 0, 5).listen();
  gui.add(params, "trail", 1, 4, 1).listen();
  gui.add(params, "random", 0, 1).listen();
  gui.add(params, "bulge", 0, 1).listen();
  gui.add(params, "polyDetail", 0, 1).listen();
  gui.add(params, "softEdge", 0, 1).listen();
}

</script>
</body>
</html> 